package com.tianji.learning.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.BooleanUtil;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.mq.message.SignInMessage;
import com.tianji.learning.pojo.vo.SignResultVO;
import com.tianji.learning.service.ISignRecordService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.List;

@Service
@RequiredArgsConstructor
public class SignRecordServiceImpl implements ISignRecordService {

    private final StringRedisTemplate redisTemplate;
    private final RabbitMqHelper mqHelper;


    /**
     * 签到
     *
     * @return
     */
    @Override
    public SignResultVO addSignRecords() {
        Long userId = UserContext.getUser();
        LocalDate now = LocalDate.now();

        // 拼接 Redis key
        String key = RedisConstants.SIGN_RECODE_KEY_PREFIX + userId + now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);

        // bitmap的offset从0开始，需要前移一位
        int offset = now.getDayOfMonth() - 1;
        // 保存签到信息，bitmap存储会返回已存储的值，如果有返回，表示已操作过
        Boolean isExist = redisTemplate.opsForValue().setBit(key, offset, true);

        if (BooleanUtil.isTrue(isExist)) {
            throw new BizIllegalException("不允许重复签到");
        }

        // 计算重复签到天数
        int signDays = countSignDays(key, now.getDayOfMonth());

        // 计算重复签到积分
        int rewardPoints = 0;
        switch (signDays) {
            case 7:
                rewardPoints = 10;
                break;
            case 14:
                rewardPoints = 20;
                break;
            case 30:
                rewardPoints = 50;
                break;
        }

        // 保存积分明细记录
        mqHelper.send(MqConstants.Exchange.LEARNING_EXCHANGE,
                MqConstants.Key.SIGN_IN,
                SignInMessage.of(userId, rewardPoints + 1));

        SignResultVO vo = new SignResultVO();
        vo.setSignDays(signDays);
        vo.setRewardPoints(rewardPoints);
        return vo;
    }

    /**
     * 获取重复签到天数
     *
     * @param key
     * @param dayOfMonth
     * @return
     */
    private int countSignDays(String key, int dayOfMonth) {
        // 获取本月从第一天开始，到今天为止的所有签到记录
        // bitmap的bitfield获取区间范围的数据
        List<Long> bitField = redisTemplate.opsForValue()
                .bitField(key, BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
                        .valueAt(0));

        if (CollUtil.isEmpty(bitField)) {
            return 0;
        }

        int num = bitField.get(0).intValue();

        // 定义一个计数器
        int count = 0;

        // bit值与1做运算，得到最后一个bit，判断是否为0，为0则终止，为1则继续
        while ((num & 1) == 1) {
            // 为1时增加，为0时不进循环直接返回
            count++;

            // 把数字右移一位
            num >>>= 1;
        }
        return count;
    }

    @Override
    public Byte[] querySignRecords() {

        // 查询签到记录
        Long userId = UserContext.getUser();
        LocalDate now = LocalDate.now();
        int dayOfMonth = now.getDayOfMonth();

        // 拼接redis key
        String key = RedisConstants.SIGN_RECODE_KEY_PREFIX + userId + now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);

        // 通过bitfield读取缓存中数据
        List<Long> bitField = redisTemplate.opsForValue()
                .bitField(key, BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType
                                .unsigned(dayOfMonth))
                        .valueAt(0));
        if (CollUtil.isEmpty(bitField)) {
            return new Byte[0];
        }

        int num = bitField.get(0).intValue();
        Byte[] arr = new Byte[dayOfMonth];

        int pos = dayOfMonth - 1;
        while (pos >= 0) {
            arr[pos--] = (byte) (num & 1);
            // 把数字右移一位，抛弃最后一个比特位，继续下一个bit位
            num >>>= 1;
        }

        return arr;
    }
}